Skip to content

[ADD] sale_product_multiple_qty#4143

Open
yankinmax wants to merge 1 commit intoOCA:19.0from
camptocamp:add-sale_product_multiple_qty
Open

[ADD] sale_product_multiple_qty#4143
yankinmax wants to merge 1 commit intoOCA:19.0from
camptocamp:add-sale_product_multiple_qty

Conversation

@yankinmax
Copy link
Copy Markdown
Contributor

@yankinmax yankinmax commented Feb 4, 2026

This module adds a Sales Multiple unit of measure on products.

When a Sales Multiple is set, sales order line quantities are automatically
rounded UP to the nearest multiple of the selected unit of measure.
This is useful for products that must be sold in fixed pack sizes
(boxes, bundles, pallets, etc.).

The rounding is performed by converting the entered quantity to the Sales
Multiple UoM, rounding the number of packs UP to the nearest integer,
and converting the result back to the order line UoM.

Being said, if sales multiple UoM is divisible by the order line UoM, the result will be an integer.
Otherwise, we expect rounding issues.

For example (compatible UoMs: 100 is divisible by 5):

  • order line UoM: Pack of 5 (5 units)
  • sales multiple UoM: Pack of 100 (100 units)
  • ordering 15 packs of 5 units (75 units) is rounded to 20 packs (100 units);
  • ordering 55 packs of 5 units (275 units) is rounded to 60 packs (300 units).

For example (fractional result: 100 is not divisible by 6):

  • order line UoM: Pack of 6 (6 units)
  • sales multiple UoM: Pack of 100 (100 units)
  • ordering 13 packs of 6 units (78 units) is rounded to 16.67 packs (100.02 units);

If the Sales Multiple UoM is not divisible by the order line UoM, the rounded
quantity may be fractional. In such cases, the module displays a warning suggesting the nearest valid integer and
prevents saving invalid quantities. It is the user's responsibility to
configure compatible units of measure.

@yankinmax yankinmax force-pushed the add-sale_product_multiple_qty branch 4 times, most recently from 0b79786 to c396b0a Compare February 4, 2026 17:04
Comment thread sale_product_multiple_qty/models/product_product.py Outdated
Comment thread sale_product_multiple_qty/models/product_product.py Outdated
Comment thread sale_product_multiple_qty/models/sale_order_line.py Outdated
Comment thread sale_product_multiple_qty/models/sale_order_line.py Outdated
Comment thread sale_product_multiple_qty/models/sale_order_line.py Outdated
Comment thread sale_product_multiple_qty/models/sale_order_line.py Outdated
Comment thread sale_product_multiple_qty/models/sale_order_line.py Outdated
Comment thread sale_product_multiple_qty/models/product_product.py Outdated
@yankinmax yankinmax force-pushed the add-sale_product_multiple_qty branch 2 times, most recently from 4b45d46 to 8f5dcd9 Compare February 6, 2026 11:59
Comment thread sale_product_multiple_qty/models/sale_order_line.py Outdated
Comment thread sale_product_multiple_qty/models/sale_order_line.py Outdated
Comment thread sale_product_multiple_qty/models/sale_order_line.py Outdated
Comment thread sale_product_multiple_qty/models/sale_order_line.py Outdated
Comment thread sale_product_multiple_qty/models/sale_order_line.py Outdated
Comment thread sale_product_multiple_qty/models/sale_order_line.py Outdated
Comment thread sale_product_multiple_qty/models/sale_order_line.py Outdated
@yankinmax yankinmax force-pushed the add-sale_product_multiple_qty branch 5 times, most recently from f3080f1 to 8a987ad Compare February 11, 2026 20:17
@yankinmax yankinmax force-pushed the add-sale_product_multiple_qty branch from aec36e8 to 0e0e9a4 Compare February 18, 2026 07:23
@yankinmax yankinmax force-pushed the add-sale_product_multiple_qty branch from 0e0e9a4 to d761679 Compare February 23, 2026 12:19
Copy link
Copy Markdown

@alexey-pelykh alexey-pelykh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the clean implementation and for incorporating all the review feedback.

The module is well-structured and the rounding logic is solid. CI passes (tests + pre-commit), and ivantodorovich already approved. Just a couple of minor observations on the current state:

Dead code in tests

_get_invalid_qty_warning_msg (and the formatLang import) in the test file appear to be leftover from a previous iteration that included validation/warning logic. Since the constraint was removed in favor of silent onchange rounding, this helper is never called. Worth cleaning up to avoid confusion.

Template compute/inverse test coverage

The product_template.py compute/inverse logic for syncing sale_multiple_uom_id between template and single-variant product is not exercised by the current tests. A small test that sets the field on a template and verifies it propagates to the variant (and vice versa) would strengthen coverage and likely address the codecov gaps.

Neither of these is blocking -- the core functionality is correct and well-tested.

Comment thread sale_product_multiple_qty/tests/test_sale_product_multiple_qty.py Outdated
Comment thread sale_product_multiple_qty/tests/test_sale_product_multiple_qty.py
@yankinmax
Copy link
Copy Markdown
Contributor Author

Hello @alexey-pelykh
Thanks for your complete review on this PR.

  1. The idea was to add a constraint on the SOL to ensure product_uom_qty is a multiple of the sales multiple UoM. But, the problem is that constraint check will be only called only when the onchange rounding is already happend. Thus we don't want to round up twice numbers already rounded. It becomes non stable validation and on floating point numbers can result in unexpected results. We decided to remove it from this PR.
    If you have any idea how to correctly handle such validation we're open to suggestions.
    Here is the PR where we try an idea to round up with "HALF-EVEN" to check if rounding is already applied, before actually proceed to round "UP" => [ADD] sale_product_multiple_qty #4151
  2. Regarding the test to check sale_multiple_uom_id on the template and product.
    This idea is inspired from the standard website_sale flow of product.template::base_unit_id which is covered by the tests:
    https://github.com/odoo/odoo/blob/d038b2c23b9f7c74d381e25276d00155bf89331e/addons/website_sale/models/product_template.py#L220-L229
    If I have some extra time in future I indeed can add a check for this, but it's not really needed I think.
  3. I've cleaned the leftovers.

@yankinmax yankinmax force-pushed the add-sale_product_multiple_qty branch from d761679 to 17419af Compare March 2, 2026 09:01
Copy link
Copy Markdown
Contributor

@vvrossem vvrossem left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 👍

Comment on lines +12 to +13
help="When set, sale order quantities are rounded up to an "
"multiple number of this unit.",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
help="When set, sale order quantities are rounded up to an "
"multiple number of this unit.",
help="When set, sale order quantities are rounded up to a "
"multiple number of this unit.",

Comment on lines +15 to +16
help="When set, sale order quantities are rounded up to an "
"multiple number of this unit.",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
help="When set, sale order quantities are rounded up to an "
"multiple number of this unit.",
help="When set, sale order quantities are rounded up to a "
"multiple number of this unit.",

If the Sales Multiple UoM is not divisible by the order line UoM, the rounded
quantity may be fractional. It is the user's responsibility to
configure compatible units of measure.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change

@OCA-git-bot
Copy link
Copy Markdown
Contributor

This PR has the approved label and has been created more than 5 days ago. It should therefore be ready to merge by a maintainer (or a PSC member if the concerned addon has no declared maintainer). 🤖

@rousseldenis rousseldenis added this to the 19.0 milestone Mar 27, 2026
return qty_rounded

@api.onchange("product_id", "product_uom_qty", "product_uom_id")
def _onchange_product_uom_qty_round_multiple(self):
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMHO, as product_uom_qty is a computed field and onchanges should be avoided, this should be changed to compute_product_uom_qty()

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ivantodorovich what do you think?

Copy link
Copy Markdown
Contributor

@ivantodorovich ivantodorovich Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAIR we used onchange to not have this UX rounding play out for programatic assignation, and keep it only for forms.

In case of programatic assignation, no autofix is done and the constraint plays out -- which is better to detect possible errors

Copy link
Copy Markdown
Contributor Author

@yankinmax yankinmax Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Our goal is to propose the rounded value to a user. That's it.
AFAIR, the input value still can be used and there is no real impact anywhere except the forms.
Moreover, we dropped an idea to have any constraints here to allow rounding with decimals, because only the UoM's that share "Unit" as a common reference and have an integer as a sales multiple.

We probably might add a tiny improvement for the rounding, so the UoM's that share "Unit" as a common reference are rounded to the nearest multiple integer.

So, this example:

order line UoM: Pack of 6 (6 units)
sales multiple UoM: Pack of 100 (100 units)
ordering 13 packs of 6 units (78 units) is rounded to 16.67 packs (100.02 units);

Will have the rounded value 17.

@rousseldenis
Copy link
Copy Markdown
Contributor

@yankinmax

@yankinmax yankinmax force-pushed the add-sale_product_multiple_qty branch from 17419af to a7aecaf Compare April 7, 2026 12:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants